package ggplinko

import (
	"encoding/json"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/gkindp"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/model/statement"
	"wzgames/internal/response"
	"wzgames/internal/secure/rng"
	"wzgames/internal/services/games"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/byteutil"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/dateutil"
	"wzgames/pkg/utils/stringutil"
)

type GPlinko struct {
	games.GGame

	zn   string
	cfg  *gkindp.PlinkoConfig
	gift *Gift
}

func NewPlinko(inst *gamep.GameInst) (games.IGameService, error) {
	plinko := &GPlinko{gift: &Gift{}}
	plinko.zn = "plinko"
	plinko.SetGameInst(inst)
	err := plinko.updateConf()
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return plinko, err
	}
	plinko.gift.Init(plinko.cfg)
	return plinko, nil
}

func init() {
	games.RegisterGame(105, NewPlinko)
	games.RegisterMarker(105, NewGMarker)
}

func (a *GPlinko) updateConf() error {
	a.Lock()
	defer a.Unlock()
	if a.cfg == nil {
		a.cfg = &gkindp.PlinkoConfig{}
	}
	err := proto.Unmarshal(a.GameInst.Conf, a.cfg)
	if err != nil {
		logger.Error("[Plinko] update Conf on proto.Unmarshal", zap.Error(err))
		return err
	}
	return nil
}

func (a *GPlinko) Update() {
	err := a.updateConf()
	if err == nil {
		a.gift.Update(a.cfg)
	}
}

func (a *GPlinko) Destroy() {

}

func (a *GPlinko) Login(tb *games.GameTable) {
	seed, nextSeed := rng.BeginSeed(tb.Player.AgtID(), tb.Player.ID())
	tb.Marker.InitServerSeed(seed, nextSeed)

	code := tb.InitBalance()
	msg := response.AcquireMsgO()
	msg.Id = 1
	msg.ControlId = 0
	xw := sfs.XWriter{}
	defer xw.Reset()
	if code != 0 {
		xw.WriteUShort(uint16(2))
		xw.WriteShortS(sfs.KeyErrorCode, code)
		xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})
	} else {
		xw.WriteUShort(uint16(6))
		xw.WriteShortS(sfs.KeyReconnectionSeconds, 0)
		xw.WriteStringS(sfs.KeyZoneName, a.zn)
		xw.WriteLongS(sfs.KeyId, int64(tb.Player.ID()))
		xw.WriteStringS(sfs.KeyUserName, tb.Player.ShowName())
		xw.WriteShortS(sfs.KeyPrivilegeId, 0)
		xw.WriteSfsArrayPre(sfs.KeyRoomList, 0)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
	a.clientInit(tb)
}

func (a *GPlinko) Logout(tb *games.GameTable) {
}

func (a *GPlinko) clientInit(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.KeyInit)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(6))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteIntS(constant.KeyValType, int32(tb.GameCurr.VType))

	xw.WriteSfsObjectPre(constant.KeyUser, uint16(3))
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyAvatar, tb.Player.UHead())
	xw.WriteStringS(constant.KeyUsername, tb.Player.ShowName())

	xw.WriteSfsArrayPre(constant.KeyFreeBets, uint16(0))

	xw.Write(a.cfg.GetConfigBin())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteIntS(constant.KeyBetPrecision, tb.Seat.Precision)

	if tb.GameCurr.VType == mcomm.ValueType_Float64Type {
		xw.WriteDoubleArrayS(constant.KeyFastBets, tb.GameCurr.FloatValueLimit.FastBets)
		xw.WriteDoubleS(constant.KeyMaxBet, tb.GameCurr.FloatValueLimit.MaxBet)
		xw.WriteDoubleS(constant.KeyMinBet, tb.GameCurr.FloatValueLimit.MinBet)
		xw.WriteDoubleS(constant.KeyDefaultBetValue, tb.GameCurr.FloatValueLimit.DefaultBetValue)
		xw.WriteDoubleS(constant.KeyMaxUserWin, tb.GameCurr.FloatValueLimit.MaxUserWin)
	} else {
		xw.WriteUtfStringArray(constant.KeyFastBets, tb.GameCurr.StrValueLimit.FastBets)
		xw.WriteStringS(constant.KeyMaxBet, tb.GameCurr.StrValueLimit.MaxBet)
		xw.WriteStringS(constant.KeyMinBet, tb.GameCurr.StrValueLimit.MinBet)
		xw.WriteStringS(constant.KeyDefaultBetValue, tb.GameCurr.StrValueLimit.DefaultBetValue)
		xw.WriteStringS(constant.KeyMaxUserWin, tb.GameCurr.StrValueLimit.MaxUserWin)
	}

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GPlinko) onLogout() {
}

func (a *GPlinko) DoMessage(tb *games.GameTable, data interface{}) {
	switch m := data.(type) {
	case *sfs.TSfsObject:
		code, cmd := a.CheckMessage(m, tb.Player.AgtID(), tb.InstID(), tb.Player.ID())
		if code != 0 {
			return
		}
		switch cmd {
		case constant.HandlerBet:
			a.betHandler(tb, m)
		case constant.HandlerServerSeed:
			a.ServerSeedHandler(tb)
		case constant.HandlerGetHistory:
			a.getBetHistory(tb, m)
		case constant.HandlerRoundDetails:
			a.getRoundDetails(tb, m)
		default:
			logger.Warn("[Plinko] invalid cmd key", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.String("CmdKey", cmd))
		}
	default:
		logger.Warn("[Plinko] unknown message on DoMessage", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()))
	}
}

func (a *GPlinko) ServerSeedHandler(tb *games.GameTable) {
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseServerSeed)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteStringS(constant.KeyServerSeed, tb.Marker.NextServerSeed256())

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GPlinko) CheckPins(pins int32) int {
	l := len(a.cfg.GameConfig.Pins)
	for i := 0; i < l; i++ {
		if a.cfg.GameConfig.Pins[i] == pins {
			return i
		}
	}
	return -1
}

func (a *GPlinko) betHandler(tb *games.GameTable, exMsg *sfs.TSfsObject) {
	data := exMsg.GetContent()
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	clientSeed := data.GetUtfString(constant.KeyClientSeed)
	if !gvalidator.IsAlphaNumeric(clientSeed) || len(clientSeed) > 32 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betValue := data.GetUtfString(constant.KeyBet)
	if !gvalidator.IsAmount(betValue) || !gloader.CommMgr().CheckAmountPointByCurrency(tb.Seat.Currency, betValue) {
		a.RetErr(tb, response.ErrAmount, constant.ResponseBet)
		return
	}
	pinsStr := data.GetUtfString(constant.KeyPins)
	pins, _ := stringutil.ToInt32(pinsStr, 0)
	pinIdx := a.CheckPins(pins)
	if pinIdx == -1 {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betType := data.GetUtfString(constant.KeyType)
	if betType != constant.KeyGreen && betType != constant.KeyRed && betType != constant.KeyYellow {
		a.RetErr(tb, response.ErrBadRequest, constant.ResponseBet)
		return
	}
	betObj := tb.GenBetObj(mcomm.GameAction_GameImResult)
	balaCode := tb.CheckBetValue(betValue)
	if balaCode != 0 {
		if balaCode == response.ErrInsufficientBalance {
			balaCode = tb.LoadBalance()
		}
		if balaCode == 0 {
			balaCode = tb.CheckBetValue(betValue)
		}
		if balaCode != 0 {
			betObj.CkCode = int32(balaCode)
			betObj.Balance = tb.BalanceStr()
			tb.PlayInputData(betObj, exMsg)
			a.RetErrWithBala(tb, balaCode, constant.ResponseBet, true)
			return
		}
	}
	tb.PlayInputData(betObj, exMsg)

	betId := tb.GenBetNo()
	if betId == 0 {
		a.RetErr(tb, response.ErrInternal, constant.ResponseBet)
		return
	}
	tb.UpBetObj(betObj)
	result := a.gift.Calculate(tb.Marker.ServerSeed(), clientSeed, pins)
	betObj.BetId = betId
	betObj.BetAmt = betValue
	betObj.EffAmt = betObj.BetAmt
	betObj.FreeBet = false
	plinkoBet := &statement.PlinkoBetInfo{
		ServerSeed: tb.Marker.ServerSeed(),
		ClientSeed: clientSeed,
		BetType:    betType,
		Pins:       pins,
		PIdx:       int32(pinIdx),
		Result:     result,
	}
	a.calcResult(tb, betObj, plinkoBet)
	nextSeed := rng.NextSeed(0, 0, 0)
	tb.Marker.RefreshSeed(nextSeed)
	b, err := json.Marshal(plinkoBet)
	if err != nil {
		logger.Error("[Plinko] bet fail on json.Marshal plinko bet", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.Error(err))
		a.RetErr(tb, response.ErrInternal, constant.ResponseBet)
		return
	}
	betObj.Detail = byteutil.SliceByteToString(b)
	code := tb.ImResultBet(betObj)
	if code != 0 {
		a.RetErrWithBala(tb, code, constant.ResponseBet, true)
		return
	}
	a.betResponse(tb, betObj, plinkoBet)
}

func (a *GPlinko) calcResult(tb *games.GameTable, betObj *statement.PlayBet, plinkoBet *statement.PlinkoBetInfo) {
	var winAmt = "0"
	plinkoBet.Multiplier = a.getPayout(int(plinkoBet.PIdx), int(plinkoBet.Result), plinkoBet.BetType)
	imResult := &statement.PlayPayout{
		BetId:   betObj.BetId,
		GameAct: betObj.GameAct,
		Win:     mcomm.GameResult_GameLose,
		GameId:  betObj.GameId,
	}

	if plinkoBet.Multiplier > 0 {
		px := decimal.NewFromFloat(plinkoBet.Multiplier)
		imResult.Payout = px.String()
		imResult.Win = calcutil.CheckGameResult(px)
		d1, _ := decimal.NewFromString(betObj.BetAmt)
		winAmt = d1.Mul(px).Truncate(tb.Seat.Precision).String()
		winAmt, _ = tb.CheckMaxWin(winAmt)
	}
	imResult.WinAmt = winAmt
	imResult.Dateline = dateutil.UnixMilli()
	betObj.ImRet = imResult
}

func (a *GPlinko) betResponse(tb *games.GameTable, betObj *statement.PlayBet, plinkoBet *statement.PlinkoBetInfo) {
	betAmt, _ := stringutil.ToFloat64(betObj.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(betObj.ImRet.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseBet)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(9))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteDoubleS(constant.KeyBalance, tb.Balance())
	xw.WriteStringS(constant.KeyCurrency, tb.Seat.Currency)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(betObj.ImRet.Win))
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
	xw.WriteDoubleS(constant.KeyPayout, plinkoBet.Multiplier)
	xw.WriteIntS(constant.KeySlot, plinkoBet.Result)
	xw.WriteStringS(constant.KeyType, plinkoBet.BetType)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}

func (a *GPlinko) getPayout(idx, slot int, betType string) float64 {
	pinsPayout := a.cfg.GameConfig.PinsPay[idx]
	var multiplier = 1.0
	switch betType {
	case constant.KeyRed:
		if slot < len(pinsPayout.Red) {
			multiplier = pinsPayout.Red[slot]
		}
	case constant.KeyGreen:
		if slot < len(pinsPayout.Green) {
			multiplier = pinsPayout.Green[slot]
		}
	case constant.KeyYellow:
		if slot < len(pinsPayout.Yellow) {
			multiplier = pinsPayout.Yellow[slot]
		}
	}

	return multiplier
}

func (a *GPlinko) getBetHistory(tb *games.GameTable, m *sfs.TSfsObject) {
	limit, offset := a.GetPageLimit(m)
	if limit == 0 {
		return
	}

	tb.GetUserBets(offset, limit)
}

func (a *GPlinko) getRoundDetails(tb *games.GameTable, m *sfs.TSfsObject) {
	data := m.GetContent()
	if data == nil {
		return
	}
	data = data.GetSFSObject(sfs.ParamId)
	if data == nil {
		return
	}
	betId, _ := stringutil.ToInt64(data.GetUtfString(constant.KeyRoundId), 0)
	if betId <= 0 {
		return
	}

	ret, code := tb.GetUserBetDetail(betId)
	if code != 0 {
		return
	}

	plinkoBet := &statement.PlinkoBetInfo{}
	err := json.Unmarshal(byteutil.StringToSliceByte(ret.Detail), plinkoBet)
	if err != nil {
		logger.Error("[Plinko] bet fail on json.Unmarshal plinko bet", zap.Int64("AgtId", tb.Player.AgtID()), zap.Uint64("Uid", tb.Player.ID()), zap.Int64("InstID", tb.InstID()), zap.Int64("BetId", betId), zap.Error(err))
		return
	}

	betAmt, _ := stringutil.ToFloat64(ret.BetAmt, 0)
	winAmt, _ := stringutil.ToFloat64(ret.WinAmt, 0)
	msg := response.AcquireMsgO13()
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteStringS(sfs.CmdKey, constant.ResponseRoundDetails)
	xw.WriteSfsObjectPre(sfs.ParamId, uint16(12))
	xw.WriteShortS(sfs.KeyCode, response.CSuccess)
	xw.WriteLongS(constant.KeyBetId, betId)
	xw.WriteDoubleS(constant.KeyBetAmount, betAmt)
	xw.WriteDoubleS(constant.KeyWinAmount, winAmt)
	xw.WriteLongS(constant.KeyTimeRound, ret.BetTime)
	xw.WriteDoubleS(constant.KeyPayout, plinkoBet.Multiplier)
	xw.WriteStringS(constant.KeyClientSeed, plinkoBet.ClientSeed)
	xw.WriteStringS(constant.KeyCurrency, ret.Curr)
	xw.WriteBoolS(constant.KeyIsFreeBetGame, ret.FreeBet)
	xw.WriteBoolS(constant.KeyWin, response.IfWin(ret.Win))
	xw.WriteStringS(constant.KeyServerSeed, plinkoBet.ServerSeed)

	xw.WriteSfsObjectPre(constant.KeyDetails, uint16(4))
	xw.WriteIntS(constant.KeySlot, plinkoBet.Result)
	xw.WriteDoubleS(constant.KeyResult, plinkoBet.Multiplier)
	xw.WriteIntS(constant.KeyPins, plinkoBet.Pins)
	xw.WriteStringS(constant.KeyType, plinkoBet.BetType)

	xw.WriteTo(msg)
	tb.Send2Client(msg)
}
